/*
 * @Des : rafTimeout 由 window 下的 requestAnimationFrame API来模拟 setTimeout 和 setInterval
 */

interface AnimationFrameRes {
  id: number
  cancel: () => void
}

/**
 * 使用requestAnimationFrame实现的延迟或间隔调用函数。
 *
 * @param fn 要调用的函数。
 * @param delay 延迟的时间，单位为毫秒，默认为0。
 * @param isInterval 是否作为间隔调用，默认为false，即单次调用。
 * @param isInterval 是否作为立即调用一次，默认为false，即不立即调用。
 * @returns raf 返回一个包含动画帧ID的对象，可用于取消动画帧。
 */
function rafTimeout(
  fn: Function,
  delay: number = 0,
  isInterval: boolean = false,
  isImmediate: boolean = false
): AnimationFrameRes {
  let start: number | null = null
  /**
   * 定义动画帧回调函数
   * @param timestamp 参数：与performance.now()的返回值相同，它表示requestAnimationFrame() 开始去执行回调函数的时刻
   */
  function timeElapse(timestamp: number) {
    if (!start) {
      start = timestamp
    }
    const elapsedTime = timestamp - start

    if (isImmediate) {
      try {
        typeof fn === 'function' && fn()
      } catch (error) {
        console.warn(`Error executing rafTimeout function:`, error)
      } finally {
        isImmediate = false
      }
    }

    if (elapsedTime >= delay) {
      try {
        typeof fn === 'function' && fn()
      } catch (error) {
        console.warn(`Error executing rafTimeout function:`, error)
      }
      if (isInterval) {
        // 如果需要间隔执行，则重置开始时间并继续安排下一次动画帧
        start = timestamp
        raf.id = window.requestAnimationFrame(timeElapse)
      }
    } else {
      // (回调函数执行次数通常是每秒 60 次)若你想在浏览器下次重绘之前继续更新下一帧动画，那么回调函数自身必须再次调用 requestAnimationFrame()。requestAnimationFrame() 是一次性的。这里只要没够时间就一直调用 requestAnimationFrame()。
      raf.id = window.requestAnimationFrame(timeElapse)
    }
  }
  /**
   * 创建一个对象用于存储动画帧的ID，并初始化动画帧来执行一次timeElapse函数
   */
  const raf: AnimationFrameRes = {
    id: window.requestAnimationFrame(timeElapse),
    cancel: () => {
      isInterval = false // 停止间隔调用，停止继续动画帧
    }
  }

  return raf
}

/**
 * @description 取消 rafTimeout 函数
 * @param raf
 */
function cancelRaf(raf: AnimationFrameRes) {
  if (raf && raf.id && typeof raf.id === 'number') {
    raf.cancel()
    window.cancelAnimationFrame(raf.id)
  } else {
    console.warn('cancelRaf received an invalid id:', raf)
  }
}

export { rafTimeout, cancelRaf }
